package com.service;

import com.config.CommandParse;
import com.databus.*;
import com.google.protobuf.GeneratedMessage;
import com.packet.PackageData;
import com.protobuf.MsgExpress;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public  class Provider extends DatabusConnector implements DatabusConnector.ServiceProvider, DatabusConnector.OnConnectChangedListener,DatabusConnector.OnPublishListener {

    private ConcurrentHashMap<String,Method> requestMap=new ConcurrentHashMap<>();
    private ConcurrentHashMap<String,Method> responseMap=new ConcurrentHashMap<>();
    private ConcurrentHashMap<String,Method> publishMap=new ConcurrentHashMap<>();
    private ConcurrentHashMap<String,Class> classMap=new ConcurrentHashMap<>();

    private List<String> lstPublishMsg=new ArrayList<>();

    public Provider()
    {
        this.setServiceProvider(this);
        this.addConnectChangedListener(this);
        this.addPublishListener(this);
        Class clazz=this.getClass();
        Method[] methods=clazz.getMethods();
        for(Method method:methods){
            RequestMapping mapping=method.getAnnotation(RequestMapping.class);
            if(mapping!=null && method.getParameterCount()==1)
            {
                Class[] types=method.getParameterTypes();
                requestMap.put(types[0].getTypeName(),method);
                classMap.put(types[0].getTypeName(),types[0]);
                Class retType=method.getReturnType();
                if(retType!=null) {
                    responseMap.put(retType.getTypeName(), method);
                    classMap.put(retType.getTypeName(),retType);
                }
            }
            PublishMapping pubMapping=method.getAnnotation(PublishMapping.class);
            if(pubMapping!=null && method.getParameterCount()==1)
            {
                Class[] types=method.getParameterTypes();
                publishMap.put(types[0].getTypeName(),method);
                classMap.put(types[0].getTypeName(),types[0]);
            }
        }
        Field[] fields=clazz.getFields();
        for(Field field:fields){
            PublishMapping pubMapping=field.getAnnotation(PublishMapping.class);
            if(pubMapping!=null)
            {
                Class c=field.getType();
                lstPublishMsg.add(c.getTypeName());
                classMap.put(c.getTypeName(),c);
            }
        }
    }

    @Override
    final  public void onRequest(PackageData request) {
        Class clazz=request.mMsg.getClass();
        Object resp=null;
        if(requestMap.containsKey(clazz.getTypeName()))
        {
            Method method=requestMap.get(clazz.getTypeName());
            try {
                resp=method.invoke(this,request.mMsg);
            } catch (Exception e) {
                Log.error(e);
                resp=MsgExpress.ErrMessage.newBuilder().setErrcode(1).setErrmsg(e.toString()).build();
            }
        }
        else
        {
            Log.error("Message not handled,type="+clazz.getTypeName());
            resp=MsgExpress.ErrMessage.newBuilder().setErrcode(1).setErrmsg("Message not handled,type="+clazz.getTypeName()).build();
        }
        if(resp !=null){
            this.Reply(request.mHeader,resp);
        }
    }

    private String getSimpleCanonicalName(Class clazz)
    {
        String simplename=clazz.getSimpleName();
        String typename=clazz.getCanonicalName();
        String packname=clazz.getPackage().getName();
        if(packname.length()>0)
            typename=typename.replace(packname+".","");
        return typename;
    }

    private void addFunction(MsgExpress.ServiceInfo.Builder serviceinfo,Class clazz,String url)
    {
        MsgExpress.FunctionInfo.Builder fun=serviceinfo.addFunctionsBuilder();
        String typename=getSimpleCanonicalName(clazz);
        fun.setFunctionname(typename);
        fun.setFunctionid(CommandParse.Hashcode(typename));
        if(url.length()>0)
            fun.setUrl(url);
        MsgExpress.ParamInfo.Builder param=fun.addParamsBuilder();
        param.setName(typename);
        param.setFulltypename(clazz.getCanonicalName());
        param.setSimpletypename(typename);
        if(GeneratedMessage.class.isAssignableFrom(clazz))
        {
            param.setType(MsgExpress.SerializeType.PROTOBUF);
        }
        else
        {
            param.setType(MsgExpress.SerializeType.JSON);
        }
    }
    @Override
    public MsgExpress.ServiceInfo getServiceInfo()
    {
        String servicename=this.getClass().getCanonicalName();
        int serviceid=CommandParse.Hashcode(servicename);
        ServiceInfo info=this.getClass().getAnnotation(ServiceInfo.class);
        if(info!=null) {
            if (info.serviceid() > 0)
                serviceid = info.serviceid();
            if (info.servicename().length() > 0)
                servicename = info.servicename();
        }
        MsgExpress.ServiceInfo.Builder serviceinfo=MsgExpress.ServiceInfo.newBuilder();

        Method[] methods=this.getClass().getMethods();
        for(Method method:methods){
            RequestMapping mapping=method.getAnnotation(RequestMapping.class);
            if(mapping!=null && method.getParameterCount()==1)
            {
                Class clazz=method.getParameterTypes()[0];
                addFunction(serviceinfo,clazz,mapping.url());
                clazz=method.getReturnType();
                if(clazz!=void.class)
                    addFunction(serviceinfo,clazz,"");
            }
        }
        Field[] fields=this.getClass().getFields();
        for(Field field:fields){
            PublishMapping pubMapping=field.getAnnotation(PublishMapping.class);
            if(pubMapping!=null)
            {
                Class clazz=field.getType();
                addFunction(serviceinfo,clazz,"");
            }
        }
        serviceinfo.setServiceid(serviceid);
        serviceinfo.setServicename(servicename);
        return serviceinfo.build();
    }

    @Override
    final public void onPublish(PackageData msg) {
        Class clazz=msg.mMsg.getClass();
        Object resp=null;
        if(publishMap.containsKey(clazz.getTypeName()))
        {
            Method method=publishMap.get(clazz.getTypeName());
            try {
                resp=method.invoke(this,msg.mMsg);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public List<Integer> getTopics()
    {
        List<Integer> topics = new ArrayList<>();
        for(String strclass:publishMap.keySet()){
            Class clazz=classMap.get(strclass);
            if(clazz==null)
                continue;
            if(GeneratedMessage.class.isAssignableFrom(clazz))
            {
                String typename=getSimpleCanonicalName(clazz);
                topics.add(CommandParse.Hashcode(typename));
            }
        }
        return topics;
    }

    @Override
    public void onConnectChanged(EConnectStatus status) {

    }


}
